package cmm;
import java.io.*;
import java.math.BigInteger;
import java.util.*;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ErrorNode;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import cmm.CMMParser.ExprContext;
import cmm.CMMParser.*;
import ui.Editor;

/**
 * Created by 小兴安岭 on 2017/11/27.
 * 专门用来报错
 */

public class MyVisitor extends CMMBaseVisitor<Number> {
    /*store int variable(a list of map,every map contains key && value)*/
    private ArrayList<HashMap<String,Integer>> intParasStack = new ArrayList<>();
    /*store double variable*/
    private ArrayList<HashMap<String,Float>> doubleParasStack = new ArrayList<>();
    /*store  double array var*/
    private ArrayList<HashMap<String,ArrayList<Float>>> doubleArrayParasStack = new ArrayList<>();
    /*store int array var*/
    private ArrayList<HashMap<String,ArrayList<Integer>>> intArrayParasStack = new ArrayList<>();
    /*to judge if it has error.*/
    public static boolean hasError = false;

    public MyVisitor() {
    }
    @Override
    public Number visitAtom(AtomContext ctx) {
        if(ctx.getChildCount()==3)
        {
            //atom = ( expr )
            return visit(ctx.expr());
        }
        else
        {
            //atom = INT | HEXINT | DOUBLE
            if(ctx.INT()!=null)
            {
                return Integer.valueOf(ctx.getChild(0).getText());
            }
            else if(ctx.HEXINT()!=null)
            {
                return new BigInteger(ctx.HEXINT().getText().substring(2),16).intValue();
            }
            else if (ctx.DOUBLE()!=null)
            {
                return Float.valueOf(ctx.getChild(0).getText());
            }
            else if(ctx.identifier()!=null)
            {
                return visit(ctx.identifier());
            }
        }
        return 0;
    }

    @Override
    public Float visitBlock(BlockContext ctx) {
        pushScope();
        if(ctx.getChildCount()==3){
            visit(ctx.statement(0));
        }else if(ctx.getChildCount()>3){
            for(int i=0;i<ctx.getChildCount()-2;i++){
                visit(ctx.statement(i));
            }
        }
        popScope();
        return null;
    }

    @Override
    public Number visitCal_expr(Cal_exprContext ctx) {
        if(ctx.getChildCount()==1){
            return visit(ctx.mul_expr(0));
        }else{
            int count = ctx.getChildCount() / 2;
            Number sum=visit(ctx.mul_expr(0));
            for(int i=1;i<=count;i++){
                if(ctx.getChild(2*i-1).getText().equals("+")){
                    if(sum instanceof Integer | visit(ctx.mul_expr(i)) instanceof Integer)
                    {
                        sum =  (sum.intValue() + visit(ctx.mul_expr(i)).intValue());
                    }
                    else
                    {
                        sum =  (sum.floatValue() + visit(ctx.mul_expr(i)).floatValue());
                    }
                }
                else{
                    if(sum instanceof Integer | visit(ctx.mul_expr(i)) instanceof Integer)
                    {
                        sum =  (sum.intValue() - visit(ctx.mul_expr(i)).intValue());
                    }
                    else
                    {
                        sum =  (sum.floatValue() - visit(ctx.mul_expr(i)).floatValue());
                    }
                }
            }
            return sum;
        }
    }

    @Override
    public Number visitExpr(ExprContext ctx) {
        if(ctx.getChildCount()==1){
            //expr = cal_expr
            return visit(ctx.cal_expr(0));
        }else{
            //expr = cal_expr(<|<=|>|>=|==|!= cal_expr)?
            if(ctx.getChild(1).getText().equals("<")){
                if(visit(ctx.cal_expr(0)).floatValue()<visit(ctx.cal_expr(1)).floatValue()){
                    return 1;
                }else {
                    return 0;
                }
            }
            else if(ctx.getChild(1).getText().equals("<=")){
                if(visit(ctx.cal_expr(0)).floatValue()<=visit(ctx.cal_expr(1)).floatValue()){
                    return 1;
                }else {
                    return 0;
                }
            }
            else if(ctx.getChild(1).getText().equals(">")){
                if(visit(ctx.cal_expr(0)).floatValue()>visit(ctx.cal_expr(1)).floatValue()){
                    return 1;
                }else {
                    return 0;
                }
            }
            else if(ctx.getChild(1).getText().equals(">=")){
                if(visit(ctx.cal_expr(0)).floatValue()>=visit(ctx.cal_expr(1)).floatValue()){
                    return 1;
                }else {
                    return 0;
                }
            }
            else if(ctx.getChild(1).getText().equals("==")){
                if(visit(ctx.cal_expr(0)).floatValue()==(visit(ctx.cal_expr(1))).floatValue()){
                    return 1;
                }else {
                    return 0;
                }
            }
            else if(ctx.getChild(1).getText().equals("!=")){
                if(visit(ctx.cal_expr(0)).floatValue()!=(visit(ctx.cal_expr(1))).floatValue()){
                    return 1;
                }else {
                    return 0;
                }
            }
            //other condition
            return  0;
        }
    }

    @Override
    public Number visitWrite_stat(Write_statContext ctx) {
        visit(ctx.expr());
        return null;
    }

    @Override
    public Number visitAssign_stat(Assign_statContext ctx) {
//        hitBreakPoint(ctx);
        if(ctx.expr()==null)
        {
            return null;
        }
        if(hasIntID(ctx.identifier().ID().getText())) {
            //assign like "a = 5;"
            getIntParaByID(ctx.identifier().ID().getText()).put(ctx.identifier().ID().getText(),visit(ctx.expr()).intValue());
        }
        else if(hasDoubleID(ctx.identifier().ID().getText())) {
            //double var assign
            getDoubleParaByID(ctx.identifier().ID().getText()).put(ctx.identifier().ID().getText(),visit(ctx.expr()).floatValue());

        }
        else if(hasIntArrayID(ctx.identifier().ID().getText())) {
            //assign like "a[i] = 5;"
            //get index
            Integer index = visit(ctx.identifier().expr()).intValue();
            if(isOutOfBounds(index,getIntArrayParaByID(ctx.identifier().ID().getText()).get(ctx.identifier().ID().getText()))) {
                //get '['
                TerminalNode lfb=ctx.identifier().getToken(4,0);
                System.err.println("Line "+lfb.getSymbol().getLine()+":"+
                        (lfb.getSymbol().getCharPositionInLine()+1)
                        +" ArrayIndexOutOfBounds: "+index);
                hasError = true;
            }else {
                getIntArrayParaByID(ctx.identifier().ID().getText()).get(ctx.identifier().ID().getText()).set(index,visit(ctx.expr()).intValue());
            }
        }
        else if(hasDoubleArrayID(ctx.identifier().ID().getText())) {
            Integer index = visit(ctx.identifier().expr()).intValue();
            //decl like double a[5];
            if(isOutOfBounds(index,getDoubleArrayParaByID(ctx.identifier().ID().getText()).get(ctx.identifier().ID().getText()))) {
                TerminalNode lfb=ctx.identifier().getToken(4,0);
                System.err.println("Line "+lfb.getSymbol().getLine()+":"+
                        (lfb.getSymbol().getCharPositionInLine()+1)
                        +" ArrayIndexOutOfBounds: "+index);
                hasError = true;
            }else {
                getDoubleArrayParaByID(ctx.identifier().ID().getText()).get(ctx.identifier().ID().getText()).set(index,visit(ctx.expr()).floatValue());
            }
        }
        else {
            TerminalNode id=ctx.identifier().ID();
            System.err.println("Line "+id.getSymbol().getLine()+":"+
                    id.getSymbol().getCharPositionInLine()
                    +" identifier \'" + id.getText()+"\'"+" doesn't exist");
            hasError = true;
        }
        return null;
    }


    @Override
    public Number visitStatement(StatementContext ctx) {
        if(ctx.getChild(0)!=null)
        {visit(ctx.getChild(0));}
        return null;
    }

    @Override
    public Number visitRead_stat(Read_statContext ctx) {
        String id = ctx.identifier().ID().getText();
        if(hasIntID(id)||hasDoubleID(id)||hasIntArrayID(id)||hasDoubleArrayID(id))
        {
            return null;
        }
        else
        {
            System.err.println("Line "+ctx.identifier().ID().getSymbol().getLine()+":"+
                    ctx.identifier().ID().getSymbol().getCharPositionInLine()+
                    " identifier \'"+ctx.identifier().ID().getText()+"\' doesn't exist");
            hasError = true;
        }
        return null;
    }

    @Override
    public Number visitProgram(ProgramContext ctx) {
        pushScope();
        isHasError(ctx);
        for(int i=0;i<ctx.getChildCount();i++)
            visit(ctx.statement(i));
        popScope();
        return null;
    }


    @Override
    public Number visitMul_expr(Mul_exprContext ctx) {
        //get ops
        ArrayList<String> ops = new ArrayList<>();
        ArrayList<String> minusOrAdd = new ArrayList<>();
        for (int i =0;i<ctx.getChildCount();i++)
        {
            if(ctx.getChild(i).getText().equals("*")||
                    ctx.getChild(i).getText().equals("/")
                    )
            {
                ops.add(ctx.getChild(i).getText());
                if(ctx.getChild(i+1).getText().equals("-"))
                {
                    minusOrAdd.add("-");
                }
                else{
                    minusOrAdd.add("+");
                }
            }
        }
        if(ops.size()==0){
            if(ctx.getChild(0).getText().equals("-"))
            {
                if(visit(ctx.atom(0)) instanceof Float)
                {
                    return - visit(ctx.atom(0)).floatValue();
                }
                else
                {
                    return - visit(ctx.atom(0)).intValue();
                }
            }
            return visit(ctx.atom(0));
        }else{
            Number sum;
            if(ctx.getChild(0).getText().equals("-"))
            {
                if(visit(ctx.atom(0)) instanceof Float)
                {
                    sum =  - visit(ctx.atom(0)).floatValue();
                }
                else
                {
                    sum =  - visit(ctx.atom(0)).intValue();
                }
            }
            else
            {
                sum = visit(ctx.atom(0));
            }
            for(int i=0;i<ops.size();i++){
                if(ops.get(i).equals("*")){

                    if((sum instanceof Integer)|(visit(ctx.atom(i+1)) instanceof Integer))
                    {
                        if(minusOrAdd.get(i).equals("-"))
                        {
                            sum =  (sum.intValue() * -(visit(ctx.atom(i+1)).intValue()));
                        }
                        else
                        {
                            sum =  (sum.intValue() * visit(ctx.atom(i+1)).intValue());
                        }
                    }
                    else
                    {
                        if(minusOrAdd.get(i).equals("-"))
                        {
                            sum =  (sum.intValue() * -(visit(ctx.atom(i+1)).floatValue()));
                        }
                        else
                        {
                            sum =  (sum.intValue() * visit(ctx.atom(i+1)).floatValue());
                        }
                    }
                }
                else{
                    if(visit(ctx.atom(i+1)).floatValue()==0)
                    {
                        TerminalNode tmp;
                        if(ctx.atom(i+1).identifier()!=null)
                        {
                            tmp = ctx.atom(i+1).identifier().ID();
                        }
                        else
                        {
                            tmp = (TerminalNode)ctx.atom(i+1).getChild(0);
                        }
                        System.err.println("Line "+tmp.getSymbol().getLine()+":"+
                                tmp.getSymbol().getCharPositionInLine()+
                                " divided by zero");
                        hasError = true;
                    }
                    else
                    {
                        if((sum instanceof Integer)|(visit(ctx.atom(i+1)) instanceof Integer))
                        {
                            if(minusOrAdd.get(i).equals("-"))
                            {
                                sum =  (sum.intValue() / -(visit(ctx.atom(i+1)).intValue()));
                            }
                            else
                            {
                                sum =  (sum.intValue() / visit(ctx.atom(i+1)).intValue());
                            }
                        }
                        else
                        {
                            if(minusOrAdd.get(i).equals("-"))
                            {
                                sum =  (sum.intValue() / -(visit(ctx.atom(i+1)).floatValue()));
                            }
                            else
                            {
                                sum =  (sum.intValue() / visit(ctx.atom(i+1)).floatValue());
                            }
                        }
                    }
                }
            }
            return sum;
        }
    }

    @Override
    public Number visitArr_decl_stat(Arr_decl_statContext ctx) {
        if(hasIDInScope(ctx.ID().getText())){
            System.err.println("Line "+ctx.ID().getSymbol().getLine()+":"+
                    ctx.ID().getSymbol().getCharPositionInLine()+
                    " identifier \'"+ctx.ID().getText()+"\' already exists");
            hasError = true;
            return null;
        }
        String size = ctx.INT().getText();
        if(ctx.TYPE().getText().equals("double")){
            //list's first element is the last size of the array
            ArrayList<Float> temp= new ArrayList<>();
            for(int i = 0;i<Integer.valueOf(size);i++)
            {
                temp.add((float)0);
            }
            //eg.  double A[3];
            getDoubleArrayParaByID(ctx.ID().getText()).put(ctx.ID().getText(), temp);
        }else{
            ArrayList<Integer> temp= new ArrayList<>();
            for(int i = 0;i<Integer.valueOf(size);i++)
            {
                temp.add(0);
            }
            getIntArrayParaByID(ctx.ID().getText()).put(ctx.ID().getText(), temp);
        }
        return null;
    }

    @Override
    public Number visitIdentifier(IdentifierContext ctx) {
        if(ctx.getChildCount()==1){
            // identifier = ID
            if(hasIntIDInScope(ctx.ID().getText())||hasDoubleIDInScope(ctx.ID().getText()))
            {
                //current scope has the ID
                if(hasIntIDInScope(ctx.ID().getText()))
                {
                    //ID is a int type
                    return intParasStack.get(intParasStack.size()-1).get(ctx.ID().getText());
                }
                else
                {
                    //ID is a double type
                    return doubleParasStack.get(doubleParasStack.size()-1).get(ctx.ID().getText());
                }
            }
            else
            {
                //current scope doesn't contain the ID
                if(hasIntID(ctx.ID().getText()))
                {
                    //ID is a int type
                    return getIntParaByID(ctx.ID().getText()).get(ctx.ID().getText());
                }
                else if(hasDoubleID(ctx.ID().getText()))
                {
                    //ID is a double type
                    return getDoubleParaByID(ctx.ID().getText()).get(ctx.ID().getText());
                }
                else
                {
                    TerminalNode id=ctx.ID();
                    System.err.println("Line "+id.getSymbol().getLine()+":"+
                            id.getSymbol().getCharPositionInLine()
                            +" identifier \'" + id.getText()+"\'"+" doesn't exist");
                    hasError = true;
                }
            }
        }else{
            //indentifier = ID [ expr ]
            int index=visit(ctx.expr()).intValue();
            if(hasIntIDInScope(ctx.ID().getText())||hasDoubleIDInScope(ctx.ID().getText()))
            {
                //current scope has the ID
                if(hasIntArrayIDInScope(ctx.ID().getText()))
                {
                    //ID is a int array
                    if(!isOutOfBounds(index,intArrayParasStack.get(intArrayParasStack.size()-1).get(ctx.ID().getText())))
                    {
                        return (float)intArrayParasStack.get(intArrayParasStack.size()-1).get(ctx.ID().getText()).get(index);
                    }
                }
                else if(hasDoubleArrayIDInScope(ctx.ID().getText()))
                {
                    //ID is a double array
                    if(!isOutOfBounds(index,doubleArrayParasStack.get(doubleArrayParasStack.size()-1).get(ctx.ID().getText())))
                    {
                        return doubleArrayParasStack.get(doubleArrayParasStack.size()-1).get(ctx.ID().getText()).get(index);
                    }
                }
            }
            else
            {
                //current scope doesn't have the ID
                if(hasIntArrayID(ctx.ID().getText()))
                {
                    //ID is a int array
                    if(!isOutOfBounds(index,getIntArrayParaByID(ctx.ID().getText()).get(ctx.ID().getText())))
                    {
                        return getIntArrayParaByID(ctx.ID().getText()).get(ctx.ID().getText()).get(index);
                    }
                }
                else if(hasDoubleArrayID(ctx.ID().getText()))
                {
                    //ID is a double array
                    if(!isOutOfBounds(index,getDoubleArrayParaByID(ctx.ID().getText()).get(ctx.ID().getText())))
                    {
                        return getDoubleArrayParaByID(ctx.ID().getText()).get(ctx.ID().getText()).get(index);
                    }
                }
                else
                {
                    TerminalNode id=ctx.ID();
                    System.err.println("Line "+id.getSymbol().getLine()+":"+
                            id.getSymbol().getCharPositionInLine()
                            +" identifier \'" + id.getText()+"\'"+" doesn't exist");
                    hasError = true;
                }
            }
        }
        return 0;
    }

    @Override
    public Number visitWhile_stat(While_statContext ctx) {
        while(visit(ctx.expr()).floatValue()!=0){
            visit(ctx.block());
        }
        return null;
    }

    @Override
    public Number visitDecl_stat(Decl_statContext ctx) {
        /*get all defining id*/
        List<TerminalNode> idTemp=ctx.ID();
        /*get all value expr*/
        List<ExprContext> exprTemp=ctx.expr();
        for(int i=0;i<idTemp.size();i++){
            if(hasIDInScope(idTemp.get(i).getText())){
                /*print the error that decl var which already exists.*/
                System.err.println("Line "+idTemp.get(i).getSymbol().getLine()+":"+
                        idTemp.get(i).getSymbol().getCharPositionInLine()+
                        " identifier \'"+idTemp.get(i).getText()+"\' already exists");
                hasError = true;
                return null;
            }
        }
        if(ctx.TYPE().getText().equals("int")){
            //int var decl
            for(int i=0;i<ctx.getChildCount();i++){
                if(idTemp.contains(ctx.getChild(i))){
                    //is ID
                    if(i+2<ctx.getChildCount()&&exprTemp.contains(ctx.getChild(i+2))){
                        //has initial value

                        getIntParaByID(ctx.getChild(i).getText()).put(ctx.getChild(i).getText(), visit(ctx.getChild(i+2)).intValue());

                    }else{
                        //just declare variable,default is zero

                        getIntParaByID(ctx.getChild(i).getText()).put(ctx.getChild(i).getText(), 0);

                    }
                }
            }

        }else{
            //double var decl
            for(int i=0;i<ctx.getChildCount();i++){
                if(idTemp.contains(ctx.getChild(i))){
                    //is ID
                    if(i+2<ctx.getChildCount()&&exprTemp.contains(ctx.getChild(i+2)))
                    {
                        //has initial value

                        //has initial value
                        getDoubleParaByID(ctx.getChild(i).getText()).put(ctx.getChild(i).getText(), visit(ctx.getChild(i+2)).floatValue());
                    }else{

                        //just declare var, give the value 0
                        getDoubleParaByID(ctx.getChild(i).getText()).put(ctx.getChild(i).getText(), (float)0);

                    }
                }
            }
        }
        return null;
    }

    @Override
    public Number visitIf_stat(If_statContext ctx) {
        //pushScope();
        if(visit(ctx.expr()).floatValue()==0){
            if(ctx.getChildCount()>5)
                visit(ctx.block(1));
        }else{
            visit(ctx.block(0));
        }
        //popScope();
        return null;
    }
    /* add new element into arrayList*/
    private void pushScope(){
        intParasStack.add(new HashMap<>());
        doubleParasStack.add(new HashMap<>());
        intArrayParasStack.add(new HashMap<>());
        doubleArrayParasStack.add(new HashMap<>());

    }
    /*delete the element which added in pushScope*/
    private void popScope(){
        intParasStack.remove(intParasStack.size()-1);
        doubleParasStack.remove(doubleParasStack.size()-1);
        intArrayParasStack.remove(intArrayParasStack.size()-1);
        doubleArrayParasStack.remove(doubleArrayParasStack.size()-1);
    }
    /*to find out if intParasStack contains ID*/
    private boolean hasIntID(String ID){
        for(int i=intParasStack.size()-1;i>=0;i--){
            if(intParasStack.get(i).containsKey(ID)){
                return true;
            }
        }
        return false;
    }
    /*to find out if intParasStack contains ID only in current scope*/
    private boolean hasIntIDInScope(String ID){
        return intParasStack.get(intParasStack.size()-1).containsKey(ID);
    }
    /*to find out if doubleParasStack contains ID*/
    private boolean hasDoubleID(String ID){
        for(int i=doubleParasStack.size()-1;i>=0;i--){
            if(doubleParasStack.get(i).containsKey(ID)){
                return true;
            }
        }
        return false;
    }
    /*to find out if doubleParasStack contains ID only in current scope*/
    private boolean hasDoubleIDInScope(String ID){
        return doubleParasStack.get(doubleParasStack.size()-1).containsKey(ID);
    }
    /*to find out if intArrayParasStack contains ID*/
    private boolean hasIntArrayID(String ID){
        for(int i=intArrayParasStack.size()-1;i>=0;i--){
            if(intArrayParasStack.get(i).containsKey(ID)){
                return true;
            }
        }
        return false;
    }
    /*to find out if intArrayParasStack contains ID only in current scope*/
    private boolean hasIntArrayIDInScope(String ID){
        return intArrayParasStack.get(intArrayParasStack.size()-1).containsKey(ID);
    }
    /*to find out if doubleArrayParasStack contains ID*/
    private boolean hasDoubleArrayID(String ID){
        for(int i=doubleArrayParasStack.size()-1;i>=0;i--){
            if(doubleArrayParasStack.get(i).containsKey(ID)){
                return true;
            }
        }
        return false;
    }
    /*to find out if doubleArrayParasStack contains ID only in current scope*/
    private boolean hasDoubleArrayIDInScope(String ID){
        return doubleArrayParasStack.get(doubleArrayParasStack.size()-1).containsKey(ID);
    }
    /*get hashmap of the int ID*/
    private HashMap<String,Integer> getIntParaByID(String ID){
        for(int i=intParasStack.size()-1;i>=0;i--){
            if(intParasStack.get(i).containsKey(ID)){
                return intParasStack.get(i);
            }
        }
        return intParasStack.get(intParasStack.size()-1);
    }
    /*get hashmap of the double ID*/
    private HashMap<String,Float> getDoubleParaByID(String ID){
        for(int i=doubleParasStack.size()-1;i>=0;i--){
            if(doubleParasStack.get(i).containsKey(ID)){
                return doubleParasStack.get(i);
            }
        }
        return doubleParasStack.get(doubleParasStack.size()-1);
    }
    /*get hashmap of the int array ID*/
    private HashMap<String, ArrayList<Integer>> getIntArrayParaByID(String ID){
        for(int i=intArrayParasStack.size()-1;i>=0;i--){
            if(intArrayParasStack.get(i).containsKey(ID)){
                return intArrayParasStack.get(i);
            }
        }
        return intArrayParasStack.get(intArrayParasStack.size()-1);
    }
    /*get hashmap of the double array ID*/
    private HashMap<String,ArrayList<Float>> getDoubleArrayParaByID(String ID){
        for(int i=doubleArrayParasStack.size()-1;i>=0;i--){
            if(doubleArrayParasStack.get(i).containsKey(ID)){
                return doubleArrayParasStack.get(i);
            }
        }
        return doubleArrayParasStack.get(doubleArrayParasStack.size()-1);
    }
    /*to judge if all stack contains the ID in current scope*/
    private boolean hasIDInScope(String ID){
        //return hasIntID(ID)||hasDoubleID(ID)||hasIntArrayID(ID)||hasDoubleArrayID(ID);
        return intParasStack.get(intParasStack.size()-1).containsKey(ID)||
                doubleParasStack.get(doubleParasStack.size()-1).containsKey(ID)||
                intArrayParasStack.get(intArrayParasStack.size()-1).containsKey(ID)||
                doubleArrayParasStack.get(doubleArrayParasStack.size()-1).containsKey(ID);
    }
    /*judge if index is out of array's bounds*/
    private boolean isOutOfBounds(int index,ArrayList list)
    {
        return index >= list.size() || index < 0;
    }
    private void isHasError(ParseTree ctx)
    {
        for(int i = 0;i<ctx.getChildCount();i++)
        {
            if(ctx.getChild(i).getChild(0)==null)
            {
                if(ctx.getChild(i) instanceof ErrorNode)
                {
                    hasError = true;
                    break;
                }
            }
            else
            {
                isHasError(ctx.getChild(i));
            }
        }
    }
}
